package sk.benko.appsresource.server;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import sk.benko.appsresource.client.model.AObject;
import sk.benko.appsresource.client.model.AValue;
import sk.benko.appsresource.client.model.AppUser;
import sk.benko.appsresource.client.model.Application;
import sk.benko.appsresource.client.model.ApplicationService;
import sk.benko.appsresource.client.model.Template;
import sk.benko.appsresource.client.model.TemplateAttribute;
import sk.benko.appsresource.client.model.TreeLevel;
import sk.benko.appsresource.client.model.ValueType;
import sk.benko.appsresource.client.model.result.CountObjectsResult;
import sk.benko.appsresource.client.model.result.CreateOrUpdateObjectResult;
import sk.benko.appsresource.client.model.result.GetObjectsResult;
import sk.benko.appsresource.client.model.result.GetSearchCountsResult;
import sk.benko.appsresource.client.model.result.GetSearchObjectsResult;
import sk.benko.appsresource.client.model.result.GetTreeLevelResult;
import sk.benko.appsresource.client.model.result.GetValuesResult;
import sk.benko.appsresource.client.model.result.UpdateValueResult;

import com.google.appengine.api.memcache.MemcacheServiceFactory;

/**
 * The server-side RPC endpoint for {@link ApplicationService}.
 *
 *
 */
@SuppressWarnings("serial")
public class ApplicationServiceImpl extends ServiceImpl implements ApplicationService {
  private static final Logger log = Logger.getLogger(ApplicationServiceImpl.class.getName());

  /**
   * A reference to a cache service.
   */
  private final CacheApplication cache = new CacheApplication(MemcacheServiceFactory
      .getMemcacheService());

  /**
   * A reference to the data store.
   */
  private final StoreDB store = new StoreDB();

  /*
   * Tree level
   */
  @Override
  public GetTreeLevelResult getTreeLevel(int langId, int tId, 
      ArrayList<TreeLevel> path, TemplateAttribute ta) 
          throws AccessDeniedException {
    return new GetTreeLevelResult(getLevel(langId, tId, path, ta));
  }

  public GetTreeLevelResult getTreeLevel(int langId, int tIdSource, 
      HashMap<Integer, ArrayList<AValue>> values, int tId, 
      ArrayList<TreeLevel> path, TemplateAttribute ta) 
          throws AccessDeniedException {
    return new GetTreeLevelResult(getLevel(langId, tIdSource, values, tId, path, 
        ta));
  }

  private ArrayList<TreeLevel> getLevel(int langId, int tId, 
      ArrayList<TreeLevel> path, TemplateAttribute ta) {
    return getLevel(langId, 0, null, tId, path, ta);
  }

  private ArrayList<TreeLevel> getLevel(int langId, int tIdSource, 
      HashMap<Integer, ArrayList<AValue>> values, int tId, 
      ArrayList<TreeLevel> path, TemplateAttribute ta) {
    ArrayList<TreeLevel> result = new ArrayList<TreeLevel>();
    final StoreDB.Api api = store.getApi();
    try {
      ArrayList<StoreDB.AValue> filter = null;
      if (tIdSource > 0) {
        // filtering choose tree
        HashMap<Integer, StoreDB.TemplateAttribute> cattrs = 
            api.getCommonAttributes(tIdSource, tId);
        filter = new ArrayList<StoreDB.AValue>();
        for (ArrayList<AValue> avs : values.values()) {
          
          if (avs != null && avs.size() > 0) {
            int i = 0;
            AValue value = avs.get(i);
            AValue defValue = value;
            while (i < avs.size() 
                && value.getLangId() != langId) {
              value = ++i < avs.size() ? avs.get(i) : defValue; 
            }
            
            StoreDB.TemplateAttribute taref = cattrs.get(value.getOaId());
            if (taref != null) {
              StoreDB.AValue dbvalue = new StoreDB.AValue(0, 0, taref.getOaId(), 0,
                  value.getValueString(), value.getLangId(), value.getValueDate(),
                  value.getValueTimestamp(), value.getValueDouble(), value.getValueRef(),
                  0);
              filter.add(dbvalue);
            }
          }
        }
      }
      
      ArrayList<StoreDB.TreeLevel> tl; 
      switch (ta.getOa().getVt().getType()) {
        case ValueType.VT_INT:
        case ValueType.VT_REAL:
          tl = api.getTreeLevelNumber(filter, tId, 
              toServerTreeLevels(path), Utils.toServerTemplateAttribute(ta));
          break;
  
        case ValueType.VT_STRING:
          tl = api.getTreeLevelString(langId, filter, tId, 
              toServerTreeLevels(path), Utils.toServerTemplateAttribute(ta));
          break;
  
        case ValueType.VT_DATETIME:
        case ValueType.VT_DATE:
          tl = api.getTreeLevelDate(filter, tId, 
              toServerTreeLevels(path), Utils.toServerTemplateAttribute(ta));
          break;
  
        case ValueType.VT_REF:
          tl = api.getTreeLevelRef(langId, filter, tId, 
              toServerTreeLevels(path), Utils.toServerTemplateAttribute(ta));
          break;
          
        default:
          tl = null;
          break;
      }
      final ArrayList<TreeLevel> items = toClientTreeLevels(tl);
      return items;
    } catch (SQLException ex) {
      log.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
    } finally {
      api.close();
    }
    
    return result;  
  }
  
  private static ArrayList<StoreDB.TreeLevel> toServerTreeLevels(
      ArrayList<TreeLevel> levels) {
    final ArrayList<StoreDB.TreeLevel> servers = new ArrayList<StoreDB.TreeLevel>();
    for (TreeLevel tl : levels) {
      servers.add(new StoreDB.TreeLevel(Utils.toServerTemplateAttribute(tl.getTa()), 
          tl.getValueString(), tl.getValueDate(), tl.getValueDouble(),
          tl.getValueRef()));
    }
    return servers;
  }

  private static ArrayList<TreeLevel> toClientTreeLevels(
      ArrayList<StoreDB.TreeLevel> levels) {
    final ArrayList<TreeLevel> servers = new ArrayList<TreeLevel>();
    for (StoreDB.TreeLevel tl : levels) {
      servers.add(new TreeLevel(Utils.toClientTemplateAttribute(tl.getTa()), 
          tl.getValueString(), tl.getValueDate(), tl.getValueDouble(),
          tl.getValueRef()));
    }
    return servers;
  }

  @Override
  public GetObjectsResult getObjects(int langId, int tId, ArrayList<TreeLevel> path, 
      TemplateAttribute ta) throws AccessDeniedException {
    return new GetObjectsResult(path, getObjs(langId, tId, path, ta));
  }

  @Override
  public GetSearchCountsResult getSearchObjectCounts(int appId, String searchString)
      throws AccessDeniedException {
    return new GetSearchCountsResult(getSearchObjCounts(appId, searchString));
  }

  @Override
  public GetSearchObjectsResult getSearchObjects(int langId, String searchString, 
      int tlId, int from, int perPage) throws AccessDeniedException {
    return new GetSearchObjectsResult(getSearchObjs(langId, searchString, tlId, 
        from, perPage));
  }

  @Override
  public GetSearchCountsResult getRelatedObjectCounts(int objId, int rel, Template t)
      throws AccessDeniedException {
    return new GetSearchCountsResult(getRelObjCounts(objId, rel, t));
  }

  @Override
  public GetSearchObjectsResult getRelatedObjects(int langId, int objId, int rel, 
      int tlId, int from, int perPage) throws AccessDeniedException {
    return new GetSearchObjectsResult(getRelObjs(langId, objId, rel, tlId, from, perPage));
  }

  private static ArrayList<AObject> toClientObjects(
      ArrayList<StoreDB.AObject> objects) {
    final ArrayList<AObject> clients = new ArrayList<AObject>();
    for (StoreDB.AObject o : objects) {
      clients.add(Utils.toClientObject(o));
    }
    return clients;
  }
  
  private ArrayList<AObject> getObjs(int langId, int tId, 
      ArrayList<TreeLevel> path, TemplateAttribute ta) 
          throws AccessDeniedException {
    ArrayList<AObject> result = new ArrayList<AObject>();
    final StoreDB.Api api = store.getApi();
    try {
      final ArrayList<AObject> objects = toClientObjects(api.getObjects(langId,
          tId, toServerTreeLevels(path), ta.getOaId()));
      return objects;
    } catch (SQLException ex) {
      log.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
    } finally {
      api.close();
    }
    
    return result;  
  }

  private HashMap<AObject, ArrayList<AValue>> getSearchObjs(int langId, 
      String searchString, int tlId, int from, int perPage) 
          throws AccessDeniedException {
    LinkedHashMap<AObject, ArrayList<AValue>> result = new LinkedHashMap<AObject, ArrayList<AValue>>();
    final StoreDB.Api api = store.getApi();
    try {
      HashMap<StoreDB.AObject, ArrayList<StoreDB.AValue>> objValues = 
          api.getSearchObjects(langId, searchString, tlId, from, perPage);
      for (Iterator<StoreDB.AObject> iterator = objValues.keySet().iterator(); 
          iterator.hasNext();) {
      
        StoreDB.AObject aobject = iterator.next();
        ArrayList<AValue> values = new ArrayList<AValue>();
        ArrayList<StoreDB.AValue> dbvalues = objValues.get(aobject); 
        if (dbvalues != null)
          for (int j = 0; j < dbvalues.size(); j++) 
            values.add(Utils.toClientValue(dbvalues.get(j)));
        result.put(Utils.toClientObject(aobject), values);
      }
    } catch (SQLException ex) {
      log.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
    } finally {
      api.close();
    }
    
    return result;  
  }

  private LinkedHashMap<AObject, ArrayList<AValue>> getRelObjs(int langId, 
      int objId, int rel, int tlId, int from, int perPage) 
          throws AccessDeniedException {
    LinkedHashMap<AObject, ArrayList<AValue>> result = new LinkedHashMap<AObject, ArrayList<AValue>>();
    final StoreDB.Api api = store.getApi();
    try {
      LinkedHashMap<StoreDB.AObject, ArrayList<StoreDB.AValue>> objValues = 
          api.getRelatedObjects(langId, objId, rel, tlId, from, perPage);
      for (Iterator<StoreDB.AObject> iterator = objValues.keySet().iterator(); 
          iterator.hasNext();) {
      
        StoreDB.AObject aobject = iterator.next();
        ArrayList<AValue> values = new ArrayList<AValue>();
        ArrayList<StoreDB.AValue> dbvalues = objValues.get(aobject); 
        if (dbvalues != null)
          for (int j = 0; j < dbvalues.size(); j++) 
            values.add(Utils.toClientValue(dbvalues.get(j)));
        result.put(Utils.toClientObject(aobject), values);
      }
    } catch (SQLException ex) {
      log.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
    } finally {
      api.close();
    }
    
    return result;  
  }

  private HashMap<Template, Integer> getSearchObjCounts(int appId, String searchString) 
      throws AccessDeniedException {
    HashMap<Template, Integer> result = new LinkedHashMap<Template, Integer>();
    final StoreDB.Api api = store.getApi();
    try {
      HashMap<StoreDB.Template, Integer> counts = 
          api.getSearchObjectCounts(appId, searchString);
      for (StoreDB.Template t : counts.keySet()) {
        result.put(Utils.toClientTemplate(t), counts.get(t));
      }
    } catch (SQLException ex) {
      log.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
    } finally {
      api.close();
    }
    
    return result;  
  }

  private HashMap<Template, Integer> getRelObjCounts(int objId, int rel, Template t) 
      throws AccessDeniedException {
    HashMap<Template, Integer> result = new LinkedHashMap<Template, Integer>();
    final StoreDB.Api api = store.getApi();
    try {
      result.put(t, api.getRelatedObjectCounts(objId, rel));
    } catch (SQLException ex) {
      log.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
    } finally {
      api.close();
    }
    
    return result;  
  }

  public CreateOrUpdateObjectResult createObject(final AObject object, 
      final ArrayList<AValue> values, final AppUser author) 
          throws AccessDeniedException {  
    final StoreDB.Api api = store.getApi();
    try {
      
      final StoreDB.AObject ao = new StoreDB.AObject(object.getId(), 
          object.getOtId(), object.getLevel(), object.getLeaf(), author.getId());
      api.saveObject(ao);
      for (AValue value : values) {
        final StoreDB.AValue avaluedb = new StoreDB.AValue(
            value.getId(), ao.getId(), value.getOaId(), value.getRank(),
            value.getValueString(), value.getLangId(), value.getValueDate(), 
            value.getValueTimestamp(), value.getValueDouble(), 
            value.getValueRef(), author.getId());
        api.saveValue(avaluedb);
      }
      api.commit();

      final HashMap<Integer, HashMap<Integer, ArrayList<AValue>>> loadedValues = 
          getVals(Utils.toClientObject(ao));
      
      final CreateOrUpdateObjectResult result = 
          new CreateOrUpdateObjectResult(ao.getId(), ao.getLastUpdatedAt(),
              loadedValues);
      return result;
    } catch (SQLException ex) {
      log.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
      api.rollback();
    } finally {
      api.close();
    }
    return null;
  }

  public String deleteObject(final AObject object, final AppUser author) {  
    final StoreDB.Api api = store.getApi();
    try {
      
      api.deleteObject(object.getOtId(), object.getId(), author.getId());
      api.commit();
      cache.deleteValues(object.getId());

      return "";
    } catch (SQLException ex) {
      log.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
      api.rollback();
    } finally {
      api.close();
    }
    return null;
  }

  /*
   * AValue
   */
  @Override
  public GetValuesResult getValues(AObject object)
      throws AccessDeniedException {
    return new GetValuesResult(getVals(object));
  }
  
  private HashMap<Integer, HashMap<Integer, ArrayList<AValue>>> getVals(
      AObject object) throws AccessDeniedException {
    HashMap<Integer, HashMap<Integer, ArrayList<AValue>>> result = 
        new HashMap<Integer, HashMap<Integer, ArrayList<AValue>>>();

    final StoreDB.Api api = store.getApi();
    try {
      HashMap<Integer, ArrayList<AValue>> values = cache.getValues(object.getId());
      if (values == null) {
        values = Utils.toClientValues(api.getValues(object.getId()));
        cache.putValues(object.getId(), values);
      }
      result.put(object.getId(), values);

      getSubValues(api, values, result);
    } catch (SQLException ex) {
      log.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
    } finally {
      api.close();
    }

    return result;
  }
 
  private void getSubValues(StoreDB.Api api,
      HashMap<Integer, ArrayList<AValue>> allvalues,
      HashMap<Integer, HashMap<Integer, ArrayList<AValue>>> result) 
          throws SQLException {
    for (Integer oaId : allvalues.keySet()) {
      ArrayList<AValue> values = allvalues.get(oaId);

      if (values != null)
        for (AValue value : values)
          if (value.getValueRef() > 0 && result.get(value.getValueRef()) == null) {
            HashMap<Integer, ArrayList<AValue>> subvalues = cache.getValues(value.getValueRef());
            if (subvalues == null) {
              subvalues = Utils.toClientValues(api.getValues(value.getValueRef()));
              cache.putValues(value.getValueRef(), subvalues);
            }

            result.put(value.getValueRef(), subvalues);
            getSubValues(api, subvalues, result);
          }
    }
  }

  public UpdateValueResult updateValue(final AValue value, final AppUser author) 
          throws AccessDeniedException {  
    final StoreDB.Api api = store.getApi();
    try {
      
      final StoreDB.AValue avaluedb = new StoreDB.AValue(
          value.getId(), value.getOId(), value.getOaId(), value.getRank(),
          value.getValueString(), value.getLangId(), value.getValueDate(), 
          value.getValueTimestamp(), value.getValueDouble(), 
          value.getValueRef(), author.getId());
      api.saveValue(avaluedb);
      api.commit();
      cache.deleteValues(value.getOId());

      final UpdateValueResult result = 
          new UpdateValueResult(avaluedb.getId(), avaluedb.getLastUpdatedAt(),
              Utils.toClientValue(avaluedb));
      return result;
    } catch (SQLException ex) {
      log.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
      api.rollback();
    } finally {
      api.close();
    }
    return null;
  }

  /*
   * Import objects
   */
  
  private static HashMap<Integer, StoreDB.TemplateAttribute> toServerTemplateAttributes(
      HashMap<Integer, TemplateAttribute> tas) {
    final HashMap<Integer, StoreDB.TemplateAttribute> servers = new HashMap<Integer, StoreDB.TemplateAttribute>();
    for (Iterator<Integer> iterator = tas.keySet().iterator(); 
        iterator.hasNext();) {
      int key = iterator.next();
      TemplateAttribute ta = tas.get(key);

      servers.put(key, new StoreDB.TemplateAttribute(ta.getId(), ta.getCode(), ta.getName(),
          ta.getDesc(), ta.getTgId(), Utils.toServerTemplateGroup(ta.getTg()), 
          ta.getOaId(), Utils.toServerObjectAttribute(ta.getOa()),
          ta.getFlags(), ta.getStyle(), ta.getTabIndex(), ta.getDef(), 
          ta.getLength(), ta.getLabelTop(), ta.getLabelLeft(), 
          ta.getLabelWidth(), ta.getLabelWidthUnit(), ta.getLabelAlign(), 
          ta.getTop(), ta.getLeft(), ta.getWidth(), 
          ta.getWidthUnit(), ta.getAlign(), 
          ta.getUnitTop(), ta.getUnitLeft(), ta.getUnitWidth(), 
          ta.getUnitWidthUnit(), ta.getUnitAlign(), 
          ta.getShared1(), ta.getShared2(), ta.getShared3(), ta.getShared4(), ta.getShared5(), 
          ta.getUserId()));
    }
    return servers;
  }

  
  @Override
  public CountObjectsResult importObjects(Application app, Template t, 
      String filename, HashMap<Integer, TemplateAttribute> map,
      HashMap<Integer, TemplateAttribute> keys, boolean onlyUpdate, 
      AppUser author) {
    final StoreDB.Api api = store.getApi();
    int count = 0;
    try {
      ArrayList<Integer> oIds = api.importObjects(author.getId(), Utils.toServerApplication(app), 
          Utils.toServerTemplate(t), filename, toServerTemplateAttributes(map), 
          toServerTemplateAttributes(keys), onlyUpdate);
      api.commit();
      count = oIds.size();
      for (Integer oId : oIds)
        cache.deleteValues(oId);
    } catch (SQLException ex) {
      api.rollback();
      log.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
    } finally {
      api.close();
    }
    return new CountObjectsResult(count);
  }

  @Override
  public CountObjectsResult removeDuplicates(Application app, Template t, 
      HashMap<Integer, TemplateAttribute> keys, AppUser author) {
    final StoreDB.Api api = store.getApi();
    int count = 0;
    try {
      //count = api.removeDuplicates(author.getId(), Utils.toServerApplication(app), 
      //    Utils.toServerTemplate(t), toServerTemplateAttributes(keys));
      api.commit();
    } catch (SQLException ex) {
      api.rollback();
      log.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
    } finally {
      api.close();
    }
    return new CountObjectsResult(count);
  }

}
